package com.apollographql.apollo.api

import com.apollographql.apollo.annotations.ApolloDeprecatedSince
import com.apollographql.apollo.api.json.MapJsonReader
import com.apollographql.apollo.api.json.MapJsonWriter
import kotlin.jvm.JvmField

/**
 * Base class for data builders.
 */
abstract class DataBuilder<out T : DataMap>(override val customScalarAdapters: CustomScalarAdapters) : DataBuilderScope {
  @Suppress("PropertyName") // prefixed with __ to avoid name clashes
  val __fields = mutableMapOf<String, Any?>()

  @Suppress("PropertyName")
  var __typename: String by __fields

  operator fun set(key: String, value: Any?) {
    __fields[key] = value
  }

  abstract fun build(): T
}

/**
 * Base class for all the typed maps used in data builders.
 */
abstract class DataMap(__fields: Map<String, Any?>) : Map<String, Any?> by __fields

/**
 * Receiver for all the `buildFoo()` functions so that they have access to the adapters.
 * Every builder also implements `BuilderScope`. Using context parameters could probably separate the concerns there.
 */
interface DataBuilderScope {
  val customScalarAdapters: CustomScalarAdapters
}

/**
 * An interface to support polymorphic fragments. For polymorphic fragments, we don't know what Builder to instantiate.
 * The user needs to explicitly opt in one of them.
 */
interface DataBuilderFactory<out T: DataBuilder<*>> {
  fun newBuilder(customScalarAdapters: CustomScalarAdapters): T
}

/**
 * Create a default [DataBuilderScope]. Typically, this is only used at the root of the DSL.
 */
fun DataBuilderScope(customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty): DataBuilderScope {
  return object : DataBuilderScope {
    override val customScalarAdapters: CustomScalarAdapters
      get() = customScalarAdapters
  }
}

@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0)
@Deprecated("GlobalBuilder removed in v5. Use DataBuilderScope(CustomScalarAdapters).")
val GlobalBuilder: DataBuilderScope
  get() = error("GlobalBuilder removed in v5. Use DataBuilderScope(CustomScalarAdapters).")

/**
 * A property delegate that stores the given property as it would be serialized in a Json
 * This is needed in Data Builders because the serializer only work from Json
 */
class BuilderProperty<T>(val adapter: Adapter<T>) {

  operator fun getValue(thisRef: DataBuilder<*>, property: kotlin.reflect.KProperty<*>): T {
    val data = thisRef.__fields[property.name]
    return adapter.fromJson(MapJsonReader(data), CustomScalarAdapters.Empty)
  }

  operator fun setValue(thisRef: DataBuilder<*>, property: kotlin.reflect.KProperty<*>, value: T) {
    thisRef.__fields[property.name] = MapJsonWriter().apply {
      adapter.toJson(this, CustomScalarAdapters.Empty, value)
    }.root()
  }
}

fun <T> adaptValue(adapter: Adapter<T>, value: T): Any? {
  return MapJsonWriter().apply {
    adapter.toJson(this, CustomScalarAdapters.Empty, value)
  }.root()
}

/**
 * Marker for data builders generated by the Apollo compiler.
 */
@DslMarker
annotation class DataBuilderDsl